import { Injectable, Inject } from '@angular/core';
import { HttpClient, HttpRequest, HttpHeaders, HttpEventType, HttpResponse } from '@angular/common/http';
import { NzMessageService } from 'ng-zorro-antd';

import { Query } from './query';
import { QueryResponse } from './query-response';
import { Response } from './response';
import { LoadingService } from '../service/loading.service';
import { I18nService } from '../i18n/i18n.service';
import { ApplicationEnv } from '../keys';

@Injectable({
  providedIn: 'root'
})
export class HttpService<U> {

  // 依据url缓存请求结果
  private cache = new Map();

  constructor(
    private nzMessageService: NzMessageService,
    private httpClient: HttpClient,
    private loadingService: LoadingService,
    private i18nService: I18nService,
    private applicationEnv: ApplicationEnv,
  ) { }

  get(url: U, params?: any, options?: any): Promise<Response> {
    return this.request(url, 'get', params, options);
  }

  post(url: U, params?: any, options?: any): Promise<Response> {
    return this.request(url, 'post', params, options);
  }

  query(url: U, queryParams?: Query<U>, options?: any): Promise<QueryResponse> {
    const params = queryParams ? queryParams.serialize() : {};
    this.clearEmptyParams(params);
    return this.request(url, 'post', params, options).then((data) => {
      return <QueryResponse>data;
    });
  }

  upload(url: U, params: any, onProgress?: Function, options?: any): Promise<Response> {
    params._upload = true;
    return this.request(url, 'post', params, options, onProgress);
  }

  uploadAndDownload(url: U, params: any, downloadFileName: string, onProgress?: Function, options?: any): Promise<any> {
    params._upload = true;
    params._downloadFileName = downloadFileName;
    options = Object.assign({ responseType: 'blob' }, options);
    return this.request(url, 'post', params, options, onProgress);
  }

  request(url: U, method: 'get' | 'post', params: any = {}, options: any = {}, onProgress?: Function): Promise<Response | any> {
    let requestUrl = url.toString();
    let fullUrl = requestUrl + '?';

    if (!requestUrl.startsWith('/') && !requestUrl.startsWith('http')) {
      requestUrl = this.applicationEnv.apiPath + requestUrl;
    }

    requestUrl.split('/')
      .filter(item => item.startsWith(':'))
      .forEach(item => {
        const fragment = item.substring(1);
        if (params[fragment]) {
          requestUrl = requestUrl.replace(item, params[fragment]);
          delete params[fragment];
        }
      });

    if (method === 'get') {
      fullUrl += Object.keys(params).map((k) => {
        return k + '=' + params[k];
      }).join('&');

      if (params._cache && this.cache.has(fullUrl)) {
        return Promise.resolve(this.cache.get(fullUrl));
      }
    }

    if (params._loading) {
      this.loadingService.show(params._loading === 'page', 100);
    }

    const preUploadResult = this.preUpload(method, params, options, onProgress);
    if (preUploadResult instanceof Promise) {
      return preUploadResult;
    } else if (preUploadResult) {
      options = preUploadResult.options;
    }

    let clientPromise;
    if (method === 'get') {
      options.params = params;
      clientPromise = this.httpClient.get(requestUrl, options).toPromise();
    } else if (method === 'post') {
      if (params._upload === true) {
        clientPromise = this.uploadPromise(requestUrl, preUploadResult.formData, options, onProgress, params._downloadFileName);
      } else {
        clientPromise = this.httpClient.post(requestUrl, params, options).toPromise();
      }
    }

    const _promise = this.wrapClientPromise(clientPromise, params, method, fullUrl);

    this.wrapCatch(_promise);

    return _promise;
  }

  private preUpload(method: string, params: any = {}, options: any = {}, onProgress?: Function) {
    if (method !== 'post' || params._upload !== true) {
      return;
    }

    const formData = new FormData();
    const fileMaxSize = Number.isInteger(params._fileMaxSize) && params._fileMaxSize > 0 ? params._fileMaxSize : 20;
    delete params._fileMaxSize;
    for (const key of Object.keys(params)) {
      formData.append(key, params[key]);
      if (params[key] instanceof File) {
        if (fileMaxSize * 1024 * 1000 <= params[key].size) {
          return Promise.reject(new Error('文件大小不能超过' + fileMaxSize + 'M!'));
        }
      }
    }

    const headers = new HttpHeaders();
    headers.append('Content-Type', 'multipart/form-data');
    headers.append('Accept', 'application/json');
    options = Object.assign({}, {
      reportProgress: !!onProgress,
      headers: headers,
    }, options);

    return { formData, options };
  }

  private uploadPromise(requestUrl: string, formData: FormData, options: any, onProgress?: Function, downloadFileName?: string) {
    const req = new HttpRequest('POST', requestUrl, formData, options);
    return new Promise((resolve, reject) => {
      this.httpClient.request(req).subscribe((event: any) => {
        if (event.type === HttpEventType.UploadProgress) {
          if (onProgress) {
            if (event.total > 0) {
              event.percent = event.loaded / event.total * 100;
            }
            onProgress(event);
          }
        } else if (event instanceof HttpResponse) {
          if (event.status === 200) {
            if (options.responseType === 'blob') {
              if (event.body.type === 'application/json') {
                const _data = new Blob([event.body]);
                const read = new FileReader();     // 创建读取器对象FileReader
                read.readAsText(_data);   // 开始读取文件
                read.onload = () => {  // 数据读完会触发onload事件
                  const data = JSON.parse(<any>(read.result)); // 转json
                  resolve(data);
                };
              } else { // 下载
                const data = new Blob([event.body], { type: 'text/xml;charset=UTF-8' });
                const downloadUrl = window.URL.createObjectURL(data);
                const anchor = document.createElement('a');
                anchor.href = downloadUrl;
                anchor.download = `${downloadFileName}`;
                anchor.click();
                window.URL.revokeObjectURL(anchor.href);
                resolve({ isSuccess: true, downloadFile: true });
              }
            } else {
              resolve(event.body);
            }
          } else {
            const error = new Error('http status not 200');
            (<any>error).msg = error.message;
            reject(error);
          }
        }
      }, (error) => {
        error.msg = error.message;
        reject(error);
      });
    });
  }

  private wrapClientPromise(clientPromise, params, method, fullUrl) {
    const _promise = clientPromise.then((res) => {

      if (res.isSuccess !== false) {
        if (params._cache && method === 'get') {
          this.cache.set(fullUrl, res);
        }
        return <Response>res;
      } else {
        let notice = '';
        if (res.msg) {
          notice = res.msg;
        } else if (res.code) {
          // 根据code 找对应的描述
        } else {
          notice = this.i18nService.getValue('@fwyc');
        }

        res._notice = notice;

        // 防止外部没有对promise进行catch处理时异常被直接显示到控制台
        res._noErrorConsole = true;
        return Promise.reject(res);
      }
    }).catch(e => {

      // 根据_noEmitOnError判断是否需要自动弹出提示异常信息
      if (params._noEmitOnError !== true && e._notice) {
        this.nzMessageService.error(e._notice);
      }

      return Promise.reject(e);
    }).finally(() => {
      if (params._loading) {
        this.loadingService.hide();
      }
    });

    return _promise;
  }

  // 屏蔽http响应异常,状态码为0,发请求的Promise没有 reject 或者 catch导致控制台有错误
  private wrapCatch(_promise) {
    const _then = _promise.then;
    let _reject, __p;
    const _rejectFn = (e) => {
      if (_reject) {
        _reject.call(__p, e);
      }
    };
    _promise.then = (resolve, reject) => {
      __p = _promise;
      _reject = reject;
      const _p = _then.call(_promise, resolve, _rejectFn);
      _p.catch = (r) => {
        __p = _p;
        _reject = r;
        return _p;
      };
      return _p;
    };
  }

  private clearEmptyParams(params: any) {
    if (!params) {
      return;
    }

    Object.keys(params).forEach((key) => {
      if (typeof params[key] === 'string' && !params[key]) {
        delete params[key];
      } else if (typeof params[key] === 'object') {
        this.clearEmptyParams(params[key]);
      }
    });
  }
}
